#include "SphereCamera.h"
#include "ViewPort.h"
#include "GL/glew.h"

namespace VIS
{
	CSphereCamera::CSphereCamera(void):m_size(10000)
	{
	}


	CSphereCamera::~CSphereCamera(void)
	{
	}

	void CSphereCamera::centerOn(float _lon, float _lat)
	{
		glLoadIdentity();
		gluLookAt(0.0, 0.0, 1000*m_size, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		glRotatef(-_lon,0,1,0);
		glRotatef(-_lat,1,0,-tan(_lon*DEG2RAD));
	}

	void CSphereCamera::getTransformMatrix()
	{
		
		glGetDoublev(GL_PROJECTION_MATRIX,md_projection);
		glGetDoublev(GL_MODELVIEW_MATRIX,md_modelView);
		glGetFloatv(GL_PROJECTION_MATRIX,m_projection);
		glGetFloatv(GL_MODELVIEW_MATRIX,m_modelView);
		glGetIntegerv(GL_VIEWPORT,m_viewPort);
	//	invMatrix(md_modelView, md_invModelView);

		//scale coefficient
		m_modelViewScale = sqrt(( pow(md_modelView[0], 2) + pow(md_modelView[4], 2) + pow(md_modelView[8], 2)
							 +	pow(md_modelView[1], 2) + pow(md_modelView[5], 2) + pow(md_modelView[9], 2)
							  + pow(md_modelView[2], 2) + pow(md_modelView[6], 2) + pow(md_modelView[10], 2)) /3 );
	
	}

	
	float CSphereCamera::get3DCoords(double _sx, double _sy, double &_3dx, double &_3dy, double &_3dz)
	{
		float depth;
		glPixelStorei(GL_PACK_ALIGNMENT, 1);
		glReadPixels(_sx, _sy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
		gluUnProject(_sx, _sy, (GLdouble)depth, md_modelView, md_projection, m_viewPort,
			&_3dx, &_3dy, &_3dz);
		return depth;
	}

	float CSphereCamera::getCur3DCoords(double &_3dx, double &_3dy, double &_3dz)
	{
		float depth;
		depth = get3DCoords((double)m_mouseX, (double)m_mouseY, _3dx, _3dy, _3dz);
		return depth;
	}

	void CSphereCamera::rotateGlobe(int _dx, int _dy)
	{
		double ax,ay,az;
		double bx,by,bz;
		double angle;

		ax = -_dy;
		ay = _dx;
		az = 0.0;
		/* Use inverse matrix to determine local axis of rotation */
		bx = m_invModelViewRefer[0]*ax + m_invModelViewRefer[4]*ay + m_invModelViewRefer[8] *az;
		by = m_invModelViewRefer[1]*ax + m_invModelViewRefer[5]*ay + m_invModelViewRefer[9] *az;
		bz = m_invModelViewRefer[2]*ax + m_invModelViewRefer[6]*ay + m_invModelViewRefer[10]*az;

		angle = vlen(ax,ay,az)/(double)(m_viewPort[2])*180.0/m_modelViewScale;

		glLoadIdentity();
		glMultMatrixd(m_modelViewRefer);
		glRotatef(angle,bx,by,bz); 
	}

	void CSphereCamera::rotateLocal(int _dx, int _dy)
	{
		//only allow a limited degree of rotation
		int maxRotate= 90;	//90 degrees
		//condition of "referLocDepth" for not pointing at black space.
		if(_dy > maxRotate || _dy < -maxRotate || m_referLocDepth <= 0 || m_referLocDepth >= 1)
			return;
			
		glLoadIdentity();
		glMultMatrixd(m_modelViewRefer);

		glTranslatef( m_referLoc3Dx, m_referLoc3Dy, m_referLoc3Dz);
		glRotatef(_dy, m_horizontalAxis[0], m_horizontalAxis[1], m_horizontalAxis[2]);
		glTranslatef(-m_referLoc3Dx,-m_referLoc3Dy,-m_referLoc3Dz);
			
		glRotatef(_dx,m_referLoc3Dx,m_referLoc3Dy,m_referLoc3Dz);
	}

	void CSphereCamera::getMatrixMouseRefer()
	{
		glGetDoublev(GL_MODELVIEW_MATRIX,m_modelViewRefer);
		invertMatrix(m_modelViewRefer,m_invModelViewRefer);
		global2localCoords(1.0, 0.0, 0.0, m_horizontalAxis[0], m_horizontalAxis[1], m_horizontalAxis[2], m_invModelViewRefer);
		global2localCoords(0.0, 1.0, 0.0, m_verticalAxis[0], m_verticalAxis[1], m_verticalAxis[2], m_invModelViewRefer);
		m_referLocDepth = getCur3DCoords(m_referLoc3Dx, m_referLoc3Dy,m_referLoc3Dz);
	}



	void CSphereCamera::global2localCoords(const double gx, const double gy, const double gz, double &lx, double &ly, double &lz, double *invModelView)
	{
		lx = invModelView[0]*gx + invModelView[4]*gy + invModelView[8] *gz;
		ly = invModelView[1]*gx + invModelView[5]*gy + invModelView[9] *gz;
		lz = invModelView[2]*gx + invModelView[6]*gy + invModelView[10]*gz;
	}


	void CSphereCamera::setupViewPort(int _width, int _height)
	{
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_TEXTURE_2D);

		glViewport(0, 0, _width, _height);
		m_top = m_size;
		m_bottom = 0 - m_top;
		m_right = m_size*(double)_width/(double)_height;
		m_left = 0 - m_right;
		m_far = 10000*m_size;
		m_near = 0 - m_far;

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(m_left, m_right, m_bottom, m_top, m_near, m_far);

		glMatrixMode(GL_MODELVIEW);

		float lon = 105;
		float lat = 0;
		glLoadIdentity();
		gluLookAt(0.0, 0.0, 1000*m_size, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		glRotatef(-lon, 0, 1, 0);
		glRotatef(-lat, 1, 0, -tan(lon*DEG2RAD));
	}

	void CSphereCamera::zoomGlobe(bool _dir)
	{
		int numDegrees = 20;
		if(!_dir)
			numDegrees = -numDegrees;
		double s = exp((double)numDegrees*0.01);

		double loc3Dx, loc3Dy, loc3Dz;
		float depth = getCur3DCoords(loc3Dx, loc3Dy, loc3Dz);

		if(depth <=0 || depth > 1)
		{
			loc3Dx = m_modelView[0];
			loc3Dy = m_modelView[5];
			loc3Dz = m_modelView[10];
		}

		glTranslatef( loc3Dx, loc3Dy, loc3Dz);
		glScalef(s,s,s);
		glTranslatef(-loc3Dx,-loc3Dy,-loc3Dz);
	}

 


template<typename T>
void CSphereCamera::invertMatrix(const T *m, T *out )
{

/* NB. OpenGL Matrices are COLUMN major. */
#define MAT(m,r,c) (m)[(c)*4+(r)]

/* Here's some shorthand converting standard (row,column) to index. */
#define m11 MAT(m,0,0)
#define m12 MAT(m,0,1)
#define m13 MAT(m,0,2)
#define m14 MAT(m,0,3)
#define m21 MAT(m,1,0)
#define m22 MAT(m,1,1)
#define m23 MAT(m,1,2)
#define m24 MAT(m,1,3)
#define m31 MAT(m,2,0)
#define m32 MAT(m,2,1)
#define m33 MAT(m,2,2)
#define m34 MAT(m,2,3)
#define m41 MAT(m,3,0)
#define m42 MAT(m,3,1)
#define m43 MAT(m,3,2)
#define m44 MAT(m,3,3)

   T det;
   T d12, d13, d23, d24, d34, d41;
   T tmp[16]; /* Allow out == in. */

   /* Inverse = adjoint / det. (See linear algebra texts.)*/

   /* pre-compute 2x2 dets for last two rows when computing */
   /* cofactors of first two rows. */
   d12 = (m31*m42-m41*m32);
   d13 = (m31*m43-m41*m33);
   d23 = (m32*m43-m42*m33);
   d24 = (m32*m44-m42*m34);
   d34 = (m33*m44-m43*m34);
   d41 = (m34*m41-m44*m31);

   tmp[0] =  (m22 * d34 - m23 * d24 + m24 * d23);
   tmp[1] = -(m21 * d34 + m23 * d41 + m24 * d13);
   tmp[2] =  (m21 * d24 + m22 * d41 + m24 * d12);
   tmp[3] = -(m21 * d23 - m22 * d13 + m23 * d12);

   /* Compute determinant as early as possible using these cofactors. */
   det = m11 * tmp[0] + m12 * tmp[1] + m13 * tmp[2] + m14 * tmp[3];

   /* Run singularity test. */
   if (det == 0.0) {
   }
   else {
      GLdouble invDet = 1.0 / det;
      /* Compute rest of inverse. */
      tmp[0] *= invDet;
      tmp[1] *= invDet;
      tmp[2] *= invDet;
      tmp[3] *= invDet;

      tmp[4] = -(m12 * d34 - m13 * d24 + m14 * d23) * invDet;
      tmp[5] =  (m11 * d34 + m13 * d41 + m14 * d13) * invDet;
      tmp[6] = -(m11 * d24 + m12 * d41 + m14 * d12) * invDet;
      tmp[7] =  (m11 * d23 - m12 * d13 + m13 * d12) * invDet;

      /* Pre-compute 2x2 dets for first two rows when computing */
      /* cofactors of last two rows. */
      d12 = m11*m22-m21*m12;
      d13 = m11*m23-m21*m13;
      d23 = m12*m23-m22*m13;
      d24 = m12*m24-m22*m14;
      d34 = m13*m24-m23*m14;
      d41 = m14*m21-m24*m11;

      tmp[8] =  (m42 * d34 - m43 * d24 + m44 * d23) * invDet;
      tmp[9] = -(m41 * d34 + m43 * d41 + m44 * d13) * invDet;
      tmp[10] =  (m41 * d24 + m42 * d41 + m44 * d12) * invDet;
      tmp[11] = -(m41 * d23 - m42 * d13 + m43 * d12) * invDet;
      tmp[12] = -(m32 * d34 - m33 * d24 + m34 * d23) * invDet;
      tmp[13] =  (m31 * d34 + m33 * d41 + m34 * d13) * invDet;
      tmp[14] = -(m31 * d24 + m32 * d41 + m34 * d12) * invDet;
      tmp[15] =  (m31 * d23 - m32 * d13 + m33 * d12) * invDet;

      memcpy(out, tmp, 16*sizeof(T));
   }

#undef m11
#undef m12
#undef m13
#undef m14
#undef m21
#undef m22
#undef m23
#undef m24
#undef m31
#undef m32
#undef m33
#undef m34
#undef m41
#undef m42
#undef m43
#undef m44
#undef MAT
}
	
		 


	
}